import json
import random
from io import BytesIO

from PIL import Image, ImageDraw, ImageFont
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from django.db.models import Count, F
from django.db.models.functions import TruncMonth
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, reverse, redirect

from appdemo import myforms
from appdemo.models import *


# Create your views here.

def home(request):
    # 查询所有的文章
    article_queryset = Article.objects.all()
    return render(request, 'home.html', locals())


def register(request):
    # 利用forms组件渲染html
    form_obj = myforms.MyRegForm()
    if request.method == 'POST':
        back_dict = {'code': 1, 'msg': ''}
        # 校验数据是否合法
        form_obj = myforms.MyRegForm(request.POST)
        if form_obj.is_valid():
            # cleaned_data:用户名, 密码, 确认密码, 邮箱, 手机号
            clean_data = form_obj.cleaned_data
            # 将确认密码去掉
            clean_data.pop('re_password')
            # 用户头像
            file_obj = request.FILES.get('avatar')
            # 针对用户头像,必须进行校验是否获取到了用户上传的头像,不然会存none
            # 在没有获取到用户上传的时候,应该使用默认值
            if file_obj:
                clean_data['avatar'] = file_obj
            # 合法直接操作数据库保存数据
            # **clean_data:将字典以关键字参数的形式传给函数
            UserInfo.objects.create_user(**clean_data)  # 创建普通用户
            # back_dict['url'] = '/login/'
            back_dict['url'] = reverse('login')
        else:
            back_dict['code'] = 2
            back_dict['msg'] = form_obj.errors
        return JsonResponse(back_dict)
    return render(request, 'register.html', locals())


# 随机生成不同的颜色
def get_random():
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)


# 利用pillow模块实现验证码的功能(第三方库,需要在终端通过pip install pillow)
# pip  install pillow
def get_code(request):
    """生成验证码的方法，返回的是图片的二进制数据"""
    img_obj = Image.new('RGB', (435, 35), get_random())
    # ImageDraw:能够控制在图上涂画
    # ImageFont:控制字体样式(能够在计算机上看到不同样式的字体,因为有.ttf结尾的文件)
    img_draw = ImageDraw.Draw(img_obj)  # 生成一个在当前图片作画的笔
    img_font = ImageFont.truetype('static/font/111.ttf', 30)  # 字体样式, 字体大小
    # 五位随机验证码, 由大写字母小写字母和数字构成
    code = ''
    for i in range(5):
        random_upper = chr(random.randint(65, 90))  # 大写字母
        random_lower = chr(random.randint(97, 122))  # 小写字母
        random_num = str(random.randint(0, 9))  # 数字
        # 从上面三种情况中随机选一种
        tem = random.choice([random_upper, random_lower, random_num])
        # 一个一个写到图片上能够控制每个字在图片的位置
        # 用画笔调用text(坐标, 字符, 颜色, 字体样式)
        # ((i+1)*70, 0):第一个字符的位置(70, 0),第二个字符的位置(140, 0),....
        img_draw.text(((i + 1) * 70, 0), tem, get_random(), img_font)
        code += tem
    # 随机验证码在登录视图函数中需要使用,我们将验证码保存到session中
    request.session['code'] = code
    print("生成的验证码：", code)
    # 直接存取图片很占内存,所以我们可以利用内存管理器模块帮我们处理文件
    io_obj = BytesIO()  ## 生成一个内存管理器对象,可以理解为文件操作的f
    img_obj.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())


def login(request):
    if request.method == 'POST':
        back_dict = {'code': 1, 'msg': ''}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        # 1.校验验证码是否正确
        # 你们自己决定是否区分大小写,我不区分(全转成大写)
        if code.upper() == request.session.get('code').upper():
            # 2.校验用户名和密码
            # 需要导入模块from django.contrib import auth
            user_obj = auth.authenticate(request, username=username,
                                         password=password)
            if user_obj:
                # 3.保存登录状态
                auth.login(request, user_obj)
                # back_dict['url'] = '/home/'
                back_dict['url'] = reverse('home')
            else:
                # 用户不存在以及密码错误
                back_dict['code'] = 2
                back_dict['msg'] = '用户名或密码错误'
        else:
            # 验证码错误
            back_dict['code'] = 3
            back_dict['msg'] = '验证码错误'
        return JsonResponse(back_dict)
    return render(request, 'login.html')


@login_required  # 装饰器，需要先登录才能退出登录
def logout(request):
    auth.logout(request)  # 退出登录，把登录信息从session清除
    return redirect('home')


@login_required
def order(request):
    return HttpResponse("订单列表...")


@login_required
def set_password(request):
    if request.method == 'POST':
        back_dict = {'code': 1, 'msg': ''}
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        # 判断旧密码是否正确
        is_right = request.user.check_password(old_password)
        if is_right:
            # 判断两次密码是否一致
            if new_password == confirm_password:
                # 修改密码
                request.user.set_password(new_password)
                request.user.save()
                back_dict['msg'] = '修改成功'
            else:
                # 两次密码不一致
                back_dict['code'] = 2
                back_dict['msg'] = '两次密码不一致'
        else:
            # 旧密码不正确
            back_dict['code'] = 3
            back_dict['msg'] = '旧密码错误'
        return JsonResponse(back_dict)


def site(request, username, **kwargs):
    # 先校验用户名对应的个人站点是否存在
    user_obj = UserInfo.objects.filter(username=username).first()
    if not user_obj:
        return render(request, 'error.html')
    blog = user_obj.blog
    # 查看当前个人站点下的所有文章
    article_queryset = Article.objects.filter(blog=blog)
    if kwargs:
        condition = kwargs.get('condition')
        param = kwargs.get('param')
        if condition == 'category':  # 筛选分类
            article_queryset = article_queryset.filter(category__id=param)
        elif condition == 'tag':  # 筛选标签
            article_queryset = article_queryset.filter(tags__id=param)
        else:  # 如果condition是archive,那么param是2024-10
            year, month = kwargs.get('param').split('-')
            article_queryset = article_queryset.filter(create_time__year=year, create_time__month=month)

    # 1. 查询当前用户的所有分类以及分类下的文章数
    category_list = (Category.objects.filter(blog=blog).
                     annotate(count_num=Count('article')).values_list('name', 'count_num', 'pk'))
    # 2. 查询当前用户的所有标签以及标签下的文章数
    tag_list = (Tag.objects.filter(blog=blog).
                annotate(count_num=Count('article')).values_list('name', 'count_num', 'pk'))
    # 3. 不同月份的文章进行统计
    # annotate(month=TruncMonth('create_time')):将日期中的月份生成新的字段叫做month
    date_list = ((Article.objects.filter(blog=blog).
                  annotate(month=TruncMonth('create_time')).  # 生成新的字段month(某年某月)
                  values('month').annotate(count_num=Count('pk'))).
                 values_list('month', 'count_num'))
    return render(request, 'site.html', locals())


# 测试左侧菜单的方法
def test_menu(request):
    username = "admin01"
    return render(request, 'test_menu.html', locals())


def article_detail(request, username, article_id):
    user_obj = UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog
    article_obj = Article.objects.filter(pk=article_id, blog=blog).first()
    if not article_obj:  # 找不到文章
        return render(request, 'error.html')
    # 获取当前文章的所有评论
    comment_queryset = Comment.objects.filter(article=article_obj)
    return render(request, 'article_detail.html', locals())


def up_or_down(request):
    """点赞或者点踩"""
    back_dict = {'code': 1, 'msg': ''}
    if request.method == 'POST':
        '''
        1.检验用户是否登录
        2.判断当前文章是否是当前有用户自己写的(自己不能点自己的文章)
        3.判断当前用户是否给这篇文章点过了
        4.通过所有校验操作数据库
        '''
        if request.user.is_authenticated:
            article_id = request.POST.get('article_id')
            is_up = request.POST.get('is_up')  # 拿到的是字符串
            is_up = json.loads(is_up)  # json格式字符串true转成成python布尔值
            article_obj = Article.objects.filter(pk=article_id).first()
            if not article_obj.blog.userinfo == request.user:  # 当前文章不是当前用户写的
                is_click = UpAndDown.objects.filter(user=request.user, article=article_obj)
                if not is_click:  # 当前用户没给当前文章点过
                    # 文章表修改up_num字段
                    # 点赞
                    if is_up:
                        Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
                        back_dict['msg'] = '点赞成功'
                    else:
                        Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
                        back_dict['msg'] = '点踩成功'
                    # 操作点赞点踩表
                    UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
                else:
                    # 当前用户给这篇文章点过了
                    back_dict['code'] = 2
                    back_dict['msg'] = '已经点过了!'
            else:
                # 当前文章是登录用户写的
                back_dict['code'] = 3
                back_dict['msg'] = '你不可以给自己点!'
        else:
            # 用户没有登录
            back_dict['code'] = 4
            back_dict['msg'] = '请先<a href="/login/">登录</a>'
    return JsonResponse(back_dict)


def comment(request):
    back_dict = {'code': 1, 'msg': ''}
    if request.method == 'POST':
        if request.user.is_authenticated:
            article_id = request.POST.get('article_id')
            content = request.POST.get('content')
            # 是回复还是评论，区别在于parent_id是否为空，为空表示评论， 不为空表示对某个评论的回复
            parent_id = request.POST.get('parent_id')
            # 直接操作数据库存储数据
            Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
            Comment.objects.create(user=request.user, article_id=article_id, content=content,
                                   parent_id=parent_id)
            back_dict['msg'] = '评论成功'
        else:
            back_dict['code'] = 2
            back_dict['msg'] = '用户未登录'
    return JsonResponse(back_dict)

@login_required
def set_avatar(request):
    """修改头像"""
    username = request.user.username
    if request.method == 'POST':
        file_obj = request.FILES.get('avatar')
        # 只能这样修改才会保存avatar/路径前缀
        user_obj = request.user
        user_obj.avatar = file_obj
        user_obj.save()
        return redirect('/home/')
    return render(request, 'set_avatar.html', locals())
